package net.sparrow.data;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import net.sparrow.Sparrow;
import net.sparrow.event.SpDataEntry;
import net.sparrow.event.SpEvent;
import net.sparrow.event.SpEventListener;
import net.sparrow.event.SpListenerEntry;
import net.sparrow.event.SpModuleEntry;
import net.sparrow.module.Module;
import net.sparrow.util.Util;

public class DataService {

	
	private ArrayList<SpModuleEntry> modules = new ArrayList<SpModuleEntry>();
	private HashMap<Class,List<SpListenerEntry>> listeners = new HashMap<Class,List<SpListenerEntry>>();
	//private HashMap<String, SpDataEntry> data = new HashMap<String, SpDataEntry>();
	private HashMap<String, String> keybinds = new HashMap<String, String>();
	
	//object: constrained to list, boolean, number, string
	
	public void addModule(Module m) {
		modules.add(new SpModuleEntry(m));
		wireEventListeners(m);
	}
	
	
	public SpModuleEntry lookupModuleEntry(String id) {
		for(SpModuleEntry m : modules) {
			if(m.contains(id)) {
				return m;
			}
		}
		Util.print("Tried to access missing data ID '" + id + "'");
		return null;
	}
	
	//this should only be used in three places:
	//1. CommandService (to handle set commands)
	//2. loadData()
	//3. module has to set other module data (very rare)
	//returns: successful set
	public boolean setData(String id, Object value) {
		SpModuleEntry modEntry = lookupModuleEntry(id);
		if(modEntry == null) { //tried to access missing data ID
			return false;
		}
		
		try {
		modEntry.setData(id, value);
		} catch(IllegalArgumentException e) {
			Util.print("Tried to set valid data ID '" + id + "' to invalid data: '" + value.toString());
			return false;
		}
		return true;
	}
	
	public boolean addToData(String id, Object value) {
		SpModuleEntry modEntry = lookupModuleEntry(id);
		if(modEntry == null) { //tried to access missing data ID
			return false;
		}
		
		try {
		modEntry.addData(id, value);
		} catch(IllegalArgumentException e) {
			Util.print("Tried to add invalid data type");
			return false;
		}
		return true;
		
	}
	
	public boolean remFromData(String id, Object value) {
		SpModuleEntry modEntry = lookupModuleEntry(id);
		if(modEntry == null) { //tried to access missing data ID
			return false;
		}
		
		try {
		modEntry.remData(id, value);
		} catch(IllegalArgumentException e) {
			Util.print("Tried to remove invalid data type");
			return false;
		}
		return true;
	}
	
	
	public Object getData(String id) {
		
		Object dataObj = null;
		for(SpModuleEntry entry : modules) {
			dataObj = entry.getData(id);
			if(dataObj != null) {
				return dataObj;
			}
		}
		Util.print("Tried to access missing data ID '" + id + "'");
		return dataObj; //will be null
	}
	
	////////Backend/Setup Stuff////////

	public void subscribe(Class clazz, Module mod, Method method) {
		List<SpListenerEntry> subscribedModulesList = listeners.get(clazz);
		if (subscribedModulesList == null ) {  //no mapping for this class has been made yet
			listeners.put(clazz, new ArrayList<SpListenerEntry>());
			subscribedModulesList = listeners.get(clazz);
		}
		subscribedModulesList.add(new SpListenerEntry(mod, method));
	}
	
	public void fireEvent(SpEvent e) {
		List<SpListenerEntry> listens = listeners.get(e.getClass());
		if (listens == null || listens.isEmpty()) { //no listeners
			return;
		}
		for(SpListenerEntry listen : listens) {
			listen.invoke(e);
		}
	}
	
	public List<String> getAllDataIds() {
		List<String> list = new ArrayList();
		for(SpModuleEntry e : modules) {
			list.addAll(e.getAllDataIds());
		}
		return list;
	}
	
	/**Checks for SpEventListener methods and registers them as listeners.
	 * @param newModule
	 */
	public void wireEventListeners(Module module) {
		List<Method> methodList = Arrays.asList(module.getClass().getMethods());
		
		for( Method m : methodList) {
			SpEventListener listenAnno = m.getAnnotation(SpEventListener.class);
			if (listenAnno == null) { //not a listener method
				continue;
			}

			if (m.getParameterTypes().length != 1) {
				Util.debug("Tried to register eventlistener to '" + m.getName() + "', but wrong parameters.'");
				Util.debug("listeners can only be in the format 'public void aListenerMethod(SpEvent eventType)'");
				continue;
			}
			Class eventType = m.getParameterTypes()[0];
			if (!SpEvent.class.isAssignableFrom(eventType)) {
				Util.debug("Listener method '" + m.getName() + "' can only listen for SpEvents.");
				continue;
			}
			Sparrow.data().subscribe(m.getParameterTypes()[0], module, m);
			Util.debug("Wired up event listener '" + m.getName() + "'.");
		}
	}
	
	//////////Keybinds///////////
	
	public void addKeybind(String key, String s) {
		keybinds.put(key, s);
	}
	
	public Set<String> getKeybindKeys() {
		return keybinds.keySet();
	}
	
	public String getKeybindCommand(String keyCode) {
		return keybinds.get(keyCode);
	}

	
}
